package com.vhall.component.service.reward.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.vhall.component.dao.reward.RewardMapper;
import com.vhall.component.entity.income.IncomeCreateDTO;
import com.vhall.component.entity.order.OrderDetailEntity;
import com.vhall.component.entity.pay.vo.PaymentParamVO;
import com.vhall.component.entity.reward.dto.RewardGiveDTO;
import com.vhall.component.entity.reward.entity.RewardEntity;
import com.vhall.component.entity.reward.vo.*;
import com.vhall.component.entity.room.dto.RoomJoinsEntityDTO;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.constant.RedisKey;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.common.utils.RandomUtil;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.service.order.IncomeService;
import com.vhall.component.service.order.OrderService;
import com.vhall.component.service.pay.NotifyService;
import com.vhall.component.service.pay.PaymentService;
import com.vhall.component.service.reward.RewardService;
import com.vhall.component.service.reward.constant.Consts;
import com.vhall.component.service.room.RoomInnerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lsl
 * @since 2021/7/6
 */
@Service
@Slf4j
public class RewardServiceImpl implements RewardService {

    @Value("${paas.apps.lite.appId}")
    public String appId;
    @Value("${paas.apps.lite.appSecret}")
    public String appSecret;

    @Value("${vhall.pay.domain}")
    private String api;

    @Value("${vhall.pay.fake}")
    private Boolean fakePay;


    @Autowired
    private NotifyService notifyService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RoomInnerService roomInnerFacade;

    @Autowired
    private RewardMapper rewardMapper;

    @Autowired
    private OrderService orderService;

    @Autowired
    private IncomeService incomeService;

    @Autowired
    private PaasService paasService;


    @Override
    public RewardGiveRspVO give(RewardGiveDTO rewardGiveDTO) {
        RewardGiveRspVO rspVO = new RewardGiveRspVO();
        // 构建订单流水号
        String bizOrderNo = makeBizOrderNo();
        rewardGiveDTO.setTradeNo(bizOrderNo);
        // 插入数据
        RewardEntity rewardEntity = new RewardEntity();
        BeanUtils.copyProperties(rewardGiveDTO, rewardEntity);
        rewardEntity.setCreatedAt(LocalDateTime.now());
        rewardMapper.insert(rewardEntity);
        // 构建map参数
        Map<String, Object> params = BeanUtil.beanToMap(rewardEntity, true, false);
        // 交易信息入缓存，该方法只在测试环境有效
        paymentService.tradeNoInsertCache(params);
        // 构建支付参数
        Object payment = getPayment(rewardGiveDTO, bizOrderNo);
        // 打赏数据上报，现在php没有上报
        BeanUtils.copyProperties(rewardEntity, rspVO);
        rspVO.setPayData(payment);
        return rspVO;

    }

    /**
     * 打赏排行信息查询
     *
     * @param reqVO    请求参数
     * @param userInfo 当前用户信息
     * @return
     */
    @Override
    public RewardRankRspVO rewarderRank(RewardRankReqVO reqVO, RoomJoinsEntityDTO userInfo) {
        String rewarderId = userInfo.getAccountId().toString();
        //打赏总次数
        String countKey = RedisKey.REWARD_COUNT + reqVO.getRoomId();
        int rewardCount = 0;
        if (redisTemplate.opsForHash().get(countKey, rewarderId) != null) {
            rewardCount = (int) redisTemplate.opsForHash().get(countKey, rewarderId);
        }

        //打赏总金额
        String amountKey = RedisKey.REWARD_RANK + reqVO.getRoomId();
        BigDecimal rewardAmount = BigDecimal.ZERO;
        if (redisTemplate.opsForZSet().score(amountKey, rewarderId) != null) {
            rewardAmount = BigDecimal.valueOf(
                    redisTemplate.opsForZSet().score(amountKey, rewarderId));
        }

        //获取排行位置
        String rankKey = RedisKey.REWARD_RANK + reqVO.getRoomId();
        int rank = 0;
        rank = getRank(rank, rankKey, rewarderId);

        RewardRankRspVO rewardRankRspVO = new RewardRankRspVO();
        BeanUtils.copyProperties(reqVO, rewardRankRspVO);
        rewardRankRspVO.setRank(rank);
        rewardRankRspVO.setRewardCount(rewardCount);
        rewardRankRspVO.setRewardAmount(rewardAmount);
        rewardRankRspVO.setRewarderNickName(userInfo.getNickname());
        rewardRankRspVO.setRewarderAvatar(userInfo.getAvatar());
        rewardRankRspVO.setRewarderId(rewarderId);
        return rewardRankRspVO;


    }

    @Override
    public RewardRankListRspVO rewardlistRank(RewardListRankReqVO reqVO) {
        RewardRankListRspVO rspVO = new RewardRankListRspVO();
        int limit = reqVO.getOffset() + reqVO.getLimit() - 1;

        String rkey = RedisKey.REWARD_RANK + reqVO.getRoomId();

        Long total = redisTemplate.opsForZSet().size(rkey);

        //排序set
        Set<ZSetOperations.TypedTuple<String>> set;
        if ("desc".equalsIgnoreCase(reqVO.getSortType())) {
            set = redisTemplate.opsForZSet().reverseRangeWithScores(rkey, reqVO.getOffset(), limit);
        } else {
            set = redisTemplate.opsForZSet().rangeWithScores(rkey, reqVO.getOffset(), limit);
        }
        if (set == null) {
            return null;
        }
        List<RewardRankListVO> result = new ArrayList<>();
        String cKey = RedisKey.REWARD_COUNT + reqVO.getRoomId();
        if (!set.isEmpty()) {
            // 获取accountIds
            List<String> ids = set.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
            // list转String
            String accountIds = JSON.toJSONString(ids);
            // 获取加入房间的用户信息
            List<RoomJoinsEntityDTO> userInfoList = roomInnerFacade.getRoomInfoByAccountIds(reqVO.getRoomId(), accountIds);
            // 重构list为map
            Map<Integer, RoomJoinsEntityDTO> userInfos = userInfoList.stream().collect(Collectors.toMap(RoomJoinsEntityDTO::getAccountId, roomJoinsEntityDTO -> roomJoinsEntityDTO));
            // 该房间下每个人打赏总次数的集合
            List<Integer> countList = redisTemplate.opsForHash().multiGet(cKey, ids);
            int i = 0;
            for (ZSetOperations.TypedTuple<String> item : set) {
                String key = item.getValue();
                Double amount = item.getScore();
                int rank = 0;
                // 获取排行位置
                rank = getRank(rank, rkey, key);
                // 根据位置获取当前rewarderId的信息
                RoomJoinsEntityDTO userInfo = userInfos.get(Integer.valueOf(key));
                String avatar = "";
                String nickname = "";
                if (userInfo != null) {
                    avatar = userInfo.getAvatar();
                    nickname = userInfo.getNickname();
                }
                result.add(new RewardRankListVO(rank, key, amount, countList.get(i), nickname, avatar));
                i++;
            }
        }
        BeanUtils.copyProperties(reqVO, rspVO);
        rspVO.setTotal(total);
        rspVO.setList(result);
        return rspVO;
    }

    /**
     * 获取打赏记录
     *
     * @param reqVO
     * @return
     */
    @Override
    public RewardRecordsRspVO rewardRecordsList(RewardRecordsListReqVO reqVO) {
        LambdaQueryWrapper<RewardEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RewardEntity::getRoomId, reqVO.getRoomId())
                .eq(StringUtils.isNotBlank(reqVO.getRewarderId()), RewardEntity::getRewarderId, reqVO.getRewarderId())
                .eq(StringUtils.isNotBlank(reqVO.getTradeNo()), RewardEntity::getTradeNo, reqVO.getTradeNo())
                .eq(StringUtils.isNotBlank(reqVO.getPayStatus()), RewardEntity::getPayStatus, reqVO.getPayStatus())
                .eq(StringUtils.isNotBlank(reqVO.getReceiverId()), RewardEntity::getReceiverId, reqVO.getReceiverId());
        // 打赏总次数
        Integer count = rewardMapper.selectCount(wrapper);
        // 排序
        if ("desc".equalsIgnoreCase(reqVO.getSortType())) {
            wrapper.last("order by created_at " + Consts.SORT_DESC
                    + " limit " + reqVO.getOffset() + " , " + reqVO.getLimit());
        } else {
            wrapper.last("order by created_at " + Consts.SORT_ASC
                    + " limit " + reqVO.getOffset() + " , " + reqVO.getLimit());
        }
        List<RewardEntity> list = rewardMapper.selectList(wrapper);
        List<RewardDTO> result = new ArrayList<>();
        RewardDTO rewardDTO;
        for (RewardEntity rewardEntity : list) {
            rewardDTO = new RewardDTO();
            BeanUtils.copyProperties(rewardEntity, rewardDTO);
            result.add(rewardDTO);
        }

        RewardRecordsRspVO rspVO = new RewardRecordsRspVO();
        BeanUtils.copyProperties(reqVO, rspVO);
        rspVO.setCount(count);
        rspVO.setList(result);
        return rspVO;

    }

    /**
     * 设置打赏支付状态
     *
     * @param reqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public RewardPayRspVO rewardPayStatusSet(RewardPayReqVO reqVO) {
        RewardPayRspVO rspVO;
        if (!fakePay) {
            rspVO = payStatusSet(reqVO);
        } else {
            Map<String, Object> tradeNoCache = paymentService.getTradeNoCache(reqVO.getTradeNo());
            rspVO = BeanUtil.toBean(tradeNoCache, RewardPayRspVO.class);
            if (rspVO != null) {
                rspVO.setPayStatus(1);
                rspVO.setCount(1);
                reqVO.setThirdPartyTradeNo(reqVO.getTradeNo());
            }
        }
        log.info("打赏记录支付状态，{}", rspVO);
        if (!Objects.isNull(rspVO) && !Objects.isNull(rspVO.getPayStatus()) && Objects.equals(rspVO.getPayStatus(), 1)) {
            String roomId = rspVO.getRoomId();
            BigDecimal daAmount = rspVO.getRewardAmount();
            Integer sourceId = 2;
            String tradeNo = reqVO.getThirdPartyTradeNo();
            Integer channel = Objects.equals(reqVO.getChannel(), "WEIXIN") ? 1 : 2;
            Integer receiverId = Integer.valueOf(rspVO.getReceiverId());
            OrderDetailEntity orderDetailEntity = new OrderDetailEntity();
            orderDetailEntity.setRoomId(roomId);
            orderDetailEntity.setAmount(daAmount);
            orderDetailEntity.setSource(sourceId);
            orderDetailEntity.setTradeNo(tradeNo);
            orderDetailEntity.setAppId(appId);
            orderDetailEntity.setChannel(channel);
            orderDetailEntity.setAccountId(receiverId);
            orderService.create(orderDetailEntity);

            IncomeCreateDTO incomeCreateDTO = new IncomeCreateDTO();
            incomeCreateDTO.setAmount(daAmount);
            incomeCreateDTO.setAppId(appId);
            incomeCreateDTO.setAccountId(receiverId);
            incomeService.create(incomeCreateDTO);

            // 支出
            BigDecimal outDaAmount = rspVO.getRewardAmount();
            Integer rewarderId = Integer.valueOf(rspVO.getRewarderId());
            orderDetailEntity.setAmount(outDaAmount);
            orderDetailEntity.setAccountId(rewarderId);
            orderDetailEntity.setStatus(1);
            orderService.create(orderDetailEntity);

            RoomsEntityDTO roomInfo = roomInnerFacade.getRoomInfo(rspVO.getRoomId());
            Map<String, Object> sendMessageParams = new HashMap<>(15);
            sendMessageParams.put("type", "reward_pay_success");
            sendMessageParams.put("room_id", rspVO.getRoomId());
            sendMessageParams.put("reward_receiver_id", rspVO.getReceiverId());
            sendMessageParams.put("reward_describe", rspVO.getDescribe());
            sendMessageParams.put("reward_amount", rspVO.getRewardAmount());
            sendMessageParams.put("reward_count", rspVO.getCount());
            sendMessageParams.put("rewarder_id", rspVO.getRewarderId());
            sendMessageParams.put("rewarder_nickname", rspVO.getRewarderNickname());
            sendMessageParams.put("rewarder_avatar", rspVO.getRewarderAvatar());
            String body = JsonUtil.toJsonString(sendMessageParams);
            paasService.sendMessage(roomInfo.getChannelId(), body);
            return rspVO;
        }
        return null;

    }

    private RewardPayRspVO payStatusSet(RewardPayReqVO reqVO) {
        LambdaQueryWrapper<RewardEntity> wrapper = Wrappers.lambdaQuery();
        Integer count;
        wrapper.eq(RewardEntity::getTradeNo, reqVO.getTradeNo()).last(" limit 1;");
        RewardEntity rewardEntity = rewardMapper.selectOne(wrapper);
        if (rewardEntity == null) {
            throw new BusinessException(BizErrorCode.COMP_REWARD_NOT_EXIST);
        }
        if (!rewardEntity.getPayStatus().equals(reqVO.getPayStatus())) {
            if (rewardEntity.getPayStatus() != 1 && reqVO.getPayStatus() == 1) {
                // 1. 更新排行榜
                redisTemplate.opsForZSet().incrementScore(
                        RedisKey.REWARD_RANK + rewardEntity.getRoomId(),
                        rewardEntity.getRewarderId(),
                        Double.parseDouble(rewardEntity.getRewardAmount().toString()));
                // 2. 更新总数
                redisTemplate.opsForHash().increment(
                        RedisKey.REWARD_COUNT + rewardEntity.getRoomId(),
                        rewardEntity.getRewarderId(), 1);
            }
            // 更新支付状态并存入db
            rewardEntity.setPayStatus(reqVO.getPayStatus());
            rewardEntity.setUpdatedAt(LocalDateTime.now());
            rewardMapper.updateById(rewardEntity);
        }
        wrapper.clear();
        wrapper.eq(RewardEntity::getRoomId, rewardEntity.getRoomId());
        // 来源id被打赏次数
        count = rewardMapper.selectCount(wrapper);
        RewardPayRspVO rspVO = new RewardPayRspVO();
        BeanUtils.copyProperties(rewardEntity, rspVO);
        rspVO.setCount(count);
        return rspVO;
    }

    private int getRank(int rank, String key, String feild) {
        if (redisTemplate.opsForZSet().reverseRank(key, feild) != null) {
            rank = Math.toIntExact(redisTemplate.opsForZSet().reverseRank(key, feild));
        }
        if (rank == 0) {
            rank = 1;
        } else {
            rank = rank + 1;
        }
        return rank;
    }

    private Object getPayment(RewardGiveDTO rewardGiveDTO, String bizOrderNo) {
        HashMap<String, Object> payParams = new HashMap<>(15);
        payParams.put("channel", rewardGiveDTO.getChannel());
        payParams.put("service_code", rewardGiveDTO.getServiceCode());
        payParams.put("biz_order_no", bizOrderNo);
        payParams.put("title", "活动打赏");
        payParams.put("detail", "活动打赏:" + rewardGiveDTO.getDescribe());
        payParams.put("total_fee", rewardGiveDTO.getRewardAmount());
        String openId = StringUtils.isBlank(rewardGiveDTO.getOpenId()) ? "" : rewardGiveDTO.getOpenId();
        payParams.put("open_id", openId);
        payParams.put("optional", "reward");
        String now = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN);
        payParams.put("app_id", appId);
        payParams.put("timestamp", now);
        payParams.put("nonce_str", MD5.create().digestHex("vss_pay_nonce_str_" + DateUtil.currentSeconds() + "_" + RandomUtil.getRandomNum(0, 99999)));
        payParams.put("notify_url", api + "/api/pay/notify");
        if (fakePay) {
            payParams.remove("notify_url");
            payParams.put("sign_at", DateUtil.currentSeconds());
            payParams.put("sign", makeSign(payParams));
            notifyService.payNotify(payParams);
            return "";
        }
        return paymentService.genPayment(BeanUtil.toBean(payParams, PaymentParamVO.class));
    }

    private String makeBizOrderNo() {
        // 获取1000到9999的随机数
        int random = RandomUtil.getRandomNum(1000, 9999);
        return "RW" + LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMddHHmmssSSSSSS") + random;
    }

    private String makeSign(Map<String, Object> params) {
        params.remove("sign");
        StringBuilder str = new StringBuilder();
        Object[] keyArr = params.keySet().toArray();
        Arrays.sort(keyArr);
        for (Object key : keyArr) {
            String keyStr = (String) key;
            if (params.get(keyStr) != null && !"null".equals(params.get(keyStr))) {
                str.append(key).append(params.get(keyStr));
            }
        }
        str = new StringBuilder(appSecret + str + appSecret);
        return MD5.create().digestHex(str.toString());
    }
}
